/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.zookeeper.server.upgrade;

import java.io.File;
import java.io.IOException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.zookeeper.server.DataTree;
import org.apache.zookeeper.server.persistence.FileTxnSnapLog;

/**
 * This class upgrades the older database to a new database for the zookeeper
 * servers. The way to run it is java -class path zookeeper.jar Upgrade dataDir
 * snapShotDir or using zookeeper scripts with zkServer -upgrade dataDir
 * snapShotDir it creates a backup in the dataDir/.bkup and snapShotDir/.bkup
 * which can be retrieved back to the snapShotDir and dataDir
 */
public class UpgradeMain {
	private static final int CURRENTVERSION = FileTxnSnapLog.VERSION;
	private static final String dirName = FileTxnSnapLog.version;
	private static final int LASTVERSION = 1;
	private static final Logger LOG = LoggerFactory.getLogger(UpgradeMain.class);
	private static final String manual = "Please take manual steps to "
			+ "sanitize your database.\n Please read the upgrade manual";
	private static final String USAGE = "Usage: UpgradeMain dataDir snapShotDir";

	public static void main(String[] argv) {
		if (argv.length < 2) {
			LOG.error(USAGE);
			System.exit(-1);
		}
		try {
			UpgradeMain upgrade = new UpgradeMain(new File(argv[0]), new File(argv[1]));
			upgrade.runUpgrade();
		} catch (Throwable th) {
			LOG.error("Upgrade Error: Please read the " + "docs for manual failure recovery ", th);
		}
	}

	File bkupdataDir;
	File bkupsnapShotDir;
	File currentdataDir;
	File currentsnapShotDir;
	File dataDir;

	File snapShotDir;

	/**
	 * upgrade class that takes the two file directories.
	 * 
	 * @param dataDir     the directory that contains the transaction logs
	 * @param snapShotDir the directory that contains the snapshots
	 */
	public UpgradeMain(File dataDir, File snapShotDir) {
		this.snapShotDir = snapShotDir;
		this.dataDir = dataDir;
		this.bkupdataDir = new File(dataDir, dirName + LASTVERSION);
		this.bkupsnapShotDir = new File(snapShotDir, dirName + LASTVERSION);
		this.currentsnapShotDir = new File(snapShotDir, dirName + CURRENTVERSION);
		this.currentdataDir = new File(dataDir, dirName + CURRENTVERSION);
	}

	/**
	 * copy files from srcdir to dstdir that have the string filter in the srcdir
	 * filenames
	 * 
	 * @param srcDir the source directory
	 * @param dstDir the destination directory
	 * @param filter the filter of filenames that need to be copied.
	 * @throws IOException
	 */
	void copyFiles(File srcDir, File dstDir, String filter) throws IOException {
		File[] list = srcDir.listFiles();
		for (File file : list) {
			String name = file.getName();
			if (name.startsWith(filter)) {
				// we need to copy this file
				File dest = new File(dstDir, name);
				LOG.info("Renaming " + file + " to " + dest);
				if (!file.renameTo(dest)) {
					throw new IOException("Unable to rename " + file + " to " + dest);
				}
			}
		}
	}

	/**
	 * create all the bkup directories and the current database directories
	 * 
	 * @throws IOException
	 */
	private void createAllDirs() throws IOException {
		String error = "backup directory " + bkupdataDir + " already exists";
		LOG.info("Creating previous version data dir " + bkupdataDir);
		if (!bkupdataDir.mkdirs()) {
			LOG.error(error);
			LOG.error(manual);
			throw new IOException(error);
		}
		LOG.info("Creating previous version snapshot dir " + bkupdataDir);
		if (!bkupsnapShotDir.mkdirs() && !bkupsnapShotDir.exists()) {
			LOG.error(error);
			LOG.error(manual);
			throw new IOException(error);
		}
		error = "current directory " + currentdataDir + " already exists";
		LOG.info("Creating current data dir " + currentdataDir);
		if (!currentdataDir.mkdirs()) {
			LOG.error(error);
			LOG.error(manual);
			throw new IOException(error);
		}
		LOG.info("Creating current snapshot dir " + currentdataDir);
		if (!currentsnapShotDir.mkdirs() && !currentsnapShotDir.exists()) {
			LOG.error(error);
			LOG.error(manual);
			throw new IOException(error);
		}
	}

	/**
	 * run the upgrade
	 * 
	 * @throws IOException
	 */
	public void runUpgrade() throws IOException {
		if (!dataDir.exists()) {
			throw new IOException(dataDir + " does not exist");
		}
		if (!snapShotDir.exists()) {
			throw new IOException(snapShotDir + " does not exist");
		}
		// create the bkup directorya
		createAllDirs();
		// copy all the files for backup
		try {
			copyFiles(dataDir, bkupdataDir, "log");
			copyFiles(snapShotDir, bkupsnapShotDir, "snapshot");
		} catch (IOException io) {
			LOG.error("Failed in backing up.");
			throw io;
		}

		// evrything is backed up
		// read old database and create
		// an old snapshot
		UpgradeSnapShotV1 upgrade = new UpgradeSnapShotV1(bkupdataDir, bkupsnapShotDir);
		LOG.info("Creating new data tree");
		DataTree dt = upgrade.getNewDataTree();
		FileTxnSnapLog filesnapLog = new FileTxnSnapLog(dataDir, snapShotDir);
		LOG.info("snapshotting the new datatree");
		filesnapLog.save(dt, upgrade.getSessionWithTimeOuts());
		// done saving.
		LOG.info("Upgrade is complete");
	}
}
